/**
 * This file is released under the GNU General Public License.
 * Refer to the COPYING file distributed with this package.
 *
 * Copyright (c) 2008-2009 WURFL-Pro srl
 */
package net.sourceforge.wurfl.core.resource;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.apache.commons.collections.map.UnmodifiableMap;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

/**
 * This immutable class represent a device defined in WURFL repository.
 * 
 * <p>
 * This class represents the WURFL device defined in XML file and patches.
 * </p>
 * 
 * @author WURFL-PRO SRL, Rome, Italy
 * @version $Id: ModelDevice.java 1045 2009-03-09 15:52:53Z filippo.deluca $
 */
public class ModelDevice implements Serializable {

	/** Serial */
	private static final long serialVersionUID = 10L;

	/** The userAgent string */
	private String userAgent;

	/** The identifier of this device */
	private String id;

	/** The identifier of fallback device */
	private String fallBack;

	/** This indicates whether this is root device */
	private boolean actualDeviceRoot;

	/** The capabilities defined by this device */
	private Map capabilities = UnmodifiableMap.decorate(new HashMap());

	/** The capabilities identifiers stored by defining group identifier */
	private Map groupsByCapability = UnmodifiableMap.decorate(new HashMap());

	/**
	 * Protected Constructor for Builder
	 */
	protected ModelDevice() {
		// Empty
	}

	/**
	 * Build ModelDevice.
	 * 
	 * @param userAgent
	 *            User-Agent defined by this device
	 * @param id
	 *            The identifier of this device.
	 * @param fallBack
	 *            The identifier of fallback device
	 * @param actualDeviceRoot
	 *            Define if this is a root device
	 * @param capabilities
	 *            The defined capabilities
	 * @param groupsByCapability
	 *            The groups mapped to capabilities name.
	 */
	public ModelDevice(String userAgent, String id, String fallBack,
			boolean actualDeviceRoot, Map capabilities, Map groupsByCapability) {

		// Checks
		Validate.notEmpty(id, "The id must be not null");
		Validate.notEmpty(fallBack, "The fallBack must be not null");
		Validate.notEmpty(userAgent, "The userAgent must be not null");
		Validate.notNull(capabilities, "The capabilities must be not null");
		Validate.notNull(groupsByCapability,
				"The groupsByCapability must be not null");
		Validate.noNullElements(capabilities.values(),
				"The capabilities can not contain null value");
		Validate.noNullElements(groupsByCapability.values(),
				"The capabilities can not contain null value");
		Validate.allElementsOfType(capabilities.values(), String.class,
				"The capabilities must be a <String,String> map");
		Validate.allElementsOfType(capabilities.keySet(), String.class,
				"The capabilities must be a <String,String> map");
		Validate.allElementsOfType(groupsByCapability.values(), String.class,
				"The capabilities must be a <String,String> map");
		Validate.allElementsOfType(groupsByCapability.keySet(), String.class,
				"The capabilities must be a <String,String> map");
		Validate.isTrue(capabilities.keySet().equals(
				groupsByCapability.keySet()),
				"The capabilities and groups must be same Set");

		this.userAgent = userAgent;
		this.id = id;
		this.fallBack = fallBack;
		this.actualDeviceRoot = actualDeviceRoot;

		this.capabilities = UnmodifiableMap.decorate(capabilities);
		this.groupsByCapability = UnmodifiableMap.decorate(groupsByCapability);
	}

	/**
	 * Return the user-agent string defined in this device. <br />
	 * For definition of user-agent see <a
	 * href="http://en.wikipedia.org/wiki/User_agent">User-Agent</a> Wikipedia
	 * page.
	 * 
	 * @return A String containing the user-agent defined for this device.
	 */
	public String getUserAgent() {
		return userAgent;
	}

	/**
	 * Return the uniques identifier string of this device.
	 * 
	 * @return A string containing this device identifier.
	 */
	public String getID() {
		return id;
	}

	/**
	 * Return the identifier of the fallback device. The fallback device is the
	 * device from this is derived. Only the <i>generic</i> device have not
	 * fallback.
	 * 
	 * @return A string containing the identifier of this device fallback.
	 */
	public String getFallBack() {
		return fallBack;
	}

	/**
	 * Return whether this device is root device. A root device represent real
	 * device.
	 * 
	 * @return true if is device is root, false otherwise.
	 */
	public boolean isActualDeviceRoot() {
		return actualDeviceRoot;
	}

	/**
	 * Return the capabilities defined by this device. Capabilities are a list
	 * of features owned by this device.<br />
	 * For detailed description of capabilities see <a
	 * href="http://wurfl.sourceforge.net/help_doc.php">capabilities
	 * documentation</a> at wurfl site.
	 * 
	 * @return UnmodifiableMap containing the capabilities identifiers and
	 *         values defined by this device.
	 */
	public Map/* String,String */getCapabilities() {

		return capabilities;
	}
	
	/**
	 * Return the capabilities identifiers and the defining groups.
	 * 
	 * @return A UnmodifiableMap of capability identifiers and the respective
	 *         defining groups identifier.
	 */
	public Map/* String,String */getGroupsByCapability() {
		return groupsByCapability;
	}
	
	// Business methods ***************************************************

	/**
	 * Return if this device define a capability.
	 * 
	 * @param name
	 *            The capability identifier.
	 * @return True if this device define the given capability, false otherwise.
	 */
	public boolean defineCapability(String name) {
		return capabilities.containsKey(name);
	}

	/**
	 * Return a capability value.
	 * 
	 * @param name
	 *            The name of capability to get value.
	 * @return The value of capability with given name.
	 */
	public String getCapability(String name) {

		assert defineCapability(name) : id + " do not define " + name;

		return (String) capabilities.get(name);
	}

	/**
	 * Return if this device define the given group.
	 * 
	 * @param group
	 *            The group identifier to probe.
	 * @return True if this device define the given group, false otherwise.
	 */
	public boolean defineGroup(String group) {

		return groupsByCapability.containsValue(group);
	}

	/**
	 * Return the groups defined by this device.
	 * 
	 * @return A Set containing the defined groups identifiers.
	 */
	public Set getGroups() {
		return new HashSet(groupsByCapability.values());
	}

	/**
	 * Return the group defining the given capability.
	 * 
	 * @param capability
	 *            The capability to get defining group.
	 * @return A String containing the identifier of the defining group.
	 */
	public String getGroupForCapability(String capability) {

		assert defineCapability(capability);

		return (String) groupsByCapability.get(capability);
	}

	/**
	 * Return the capabilities defined by the given group.
	 * 
	 * @param group
	 *            The group defining capabilities to get.
	 * @return A set containing the identifiers of the capabilities defined by
	 *         the given group.
	 */
	public Set getCapabilitiesNamesForGroup(String group) {

		assert defineGroup(group);

		Set groupCapabilities = new HashSet();

		for (Iterator gIt = groupsByCapability.entrySet().iterator(); gIt
				.hasNext();) {
			Entry entry = (Entry) gIt.next();

			String defineGroup = (String) entry.getValue();

			if (defineGroup.equals(group)) {
				groupCapabilities.add(entry.getKey());
			}
		}

		return groupCapabilities;

	}

	/**
	 * Return the capabilities identifiers defined by a group.
	 * 
	 * @param group
	 *            The defining group of the capabilities to get.
	 * @return A Map containing the capabilities identifiers and values defined
	 *         by the given group.
	 */
	public Map getCapabilitiesForGroup(String group) {

		Map groupCapabilities = new HashMap();

		Set capabilitiesNames = getCapabilitiesNamesForGroup(group);

		for (Iterator cnIt = capabilitiesNames.iterator(); cnIt.hasNext();) {
			String name = (String) cnIt.next();
			groupCapabilities.put(name, capabilities.get(name));
		}

		return groupCapabilities;
	}



	// Commons methods ****************************************************

	public int hashCode() {
		
		
		HashCodeBuilder hb = new HashCodeBuilder(11,45);
		
		hb.append(getClass()).append(id);
		
		return hb.toHashCode();
	}

	public boolean equals(Object obj) {
		EqualsBuilder eb = new EqualsBuilder();

		eb.appendSuper(getClass().isInstance(obj));

		if (eb.isEquals()) {
			ModelDevice other = (ModelDevice) obj;

			eb.append(id, other.id);
		}

		return eb.isEquals();
	}

	public String toString() {
		
		ToStringBuilder tb = new ToStringBuilder(this);
		
		tb.append(id);

		return tb.toString();
	}

	// Helper classes *****************************************************

	/**
	 * Builder used to build ModelDevice instance.
	 */
	public static final class Builder {

		/** Held modelDevice */
		private ModelDevice device;

		public Builder(String id, String userAgent, String fallbakId) {

			device = new ModelDevice();
			device.id = id;
			device.userAgent = userAgent;
			device.fallBack = fallbakId;
		}

		public Builder setActualDeviceRoot(boolean actualDeviceRoot) {
			device.actualDeviceRoot = actualDeviceRoot;
			return this;
		}

		public Builder setCapabilities(Map capabilities) {
			device.capabilities = capabilities;
			return this;
		}

		public Builder setCapabilitiesByGroup(Map capabilitiesByGroup) {
			device.groupsByCapability = capabilitiesByGroup;
			return this;
		}

		public ModelDevice build() {

			return device;
		}
	}

}
